Duet Package Tutorial: Single Person Analysis

This tutorial demonstrates a complete workflow for analyzing OpenPose data from a single individual using the duet R package.


1. Setup and Installation

Install Required Packages

The chunk below will install any packages you are missing. It is set to eval=FALSE so it does not run automatically. Run it manually in the R console if you need to install packages for the first time.

# Install duet from GitHub if you haven't already
# devtools::install_github("themisefth/duet")

# Core packages for this workflow
required_packages <- c(
  "tidyverse", "cowplot", "rempsyc", "magick", "flextable", "remotes")

# Install any missing packages from the list
new_packages <- required_packages[!(required_packages %in% installed.packages()[,"Package"])]
if(length(new_packages)) install.packages(new_packages)

Load Libraries

library(tidyverse)
library(duet)
library(magick)
library(ggplot2)
library(cowplot)
library(rempsyc)
library(flextable)

# Set a consistent theme for ggplot2 plots
theme_set(theme_classic(base_size = 12))

Create Directory Structure

This will create the necessary folders in your project directory to store raw data and outputs.

output_dirs <- c("./openpose_json", "./openpose_csv", "./figures")
for(dir_path in output_dirs) {
  if (!dir.exists(dir_path)) dir.create(dir_path, recursive = TRUE)
}

2. Data Conversion: JSON to CSV

The first step is to convert the raw JSON files from OpenPose into a single, tidy CSV file.

Understanding Your Data Structure

Place your raw OpenPose JSON files in a dedicated folder. For this example, we assume they are in ./openpose_json/Individual_01/.

./openpose_json/
├── Individual_01/
│   ├── frame_000001_keypoints.json
│   ├── frame_000002_keypoints.json
│   └── ...
└── ...

Convert JSON to a Single Person CSV

This function reads all JSON files in the specified directory and converts them into one CSV file. We set export_type = 'person' and specify person = 1 to ensure we only process data for the first person detected by OpenPose.

op_create_csv(
  input_path = './openpose_json/Dyad_10',
  output_path = './openpose_csv/Dyad_10',
  model = 'body',
  include_filename = TRUE,
  export_type = 'dyad')

3. Data Import and Initial Checks

Import Data

Load the CSV file you just created.

# Read the CSV file into a dataframe
df <- read_csv('./openpose_csv/Dyad_10/Dyad-10.0_B_IDs-B013-B016_body_dyad.csv')

Check Data

It’s good practice to visually inspect a frame to ensure the data has loaded correctly.

# Plot a single frame to check the pose
op_plot_openpose(df, 
                 person = "both",
                 frame_num = 10, 
                 lines = TRUE)


4. Data Quality and Preprocessing

Assess Data Quality

The op_plot_quality() function helps you understand the completeness (how many keypoints were detected) and confidence (OpenPose’s certainty) of your data over time.

# First, apply human-readable labels to the keypoints
df_labelled <- op_apply_keypoint_labels(df)

# Plot data completeness
p_completeness <- op_plot_quality(df_labelled, 
                                  plot_type = 'completeness',
                                  threshold_line = 80) # 80% threshold line

# Plot tracking confidence
p_confidence <- op_plot_quality(df_labelled, 
                                plot_type = 'confidence',
                                threshold_line = 80) # Confidence threshold of 80%

# Combine plots for a comprehensive view
p_quality_combined <- plot_grid(p_completeness, p_confidence, ncol = 1, labels = 'auto')

p_quality_combined

Summarise your data

At any stage you can produce summary statistics (mean, median, skew etc) for your df

op_summarise(df)

Visualise Time Series

Plot the movement of specific keypoints over time to identify noise or gaps.

# Plot the x and y coordinates for Nose (1) and Wrists (4, 7)
p_timeseries <- op_plot_timeseries(df, 
                                   free_y = TRUE, # Allow y-axes to have different scales
                                   overlay = TRUE, # Overlay x and y on the same plot
                                   max_facets = 20) 

p_timeseries

Interpolate missing data

df_interp <- op_interpolate(df, 
                            confidence_threshold = .3, # Set a confidence threshold of 80%
                            treat_na_conf_as_low = TRUE,
                            handle_zeros = TRUE,
                            handle_missing = TRUE)

p_timeseries_interp <- op_plot_timeseries(df_interp, 
                                   free_y = TRUE, # Allow y-axes to have different scales
                                   overlay = TRUE, # Overlay x and y on the same plot
                                   max_facets = 20) 

p_timeseries_interp

Smooth Time Series Data

Use a moving average to smooth the data, which is often necessary before computing velocity or acceleration. Here, we smooth the x-coordinate of the Neck keypoint as an example.

# Apply moving-average smoothing and plot the result
op_smooth_timeseries(df, 
                     keypoints = c('x1'), # Keypoint '1' is the Neck
                     method = 'rollmean',
                     rollmean_width = 5, # 5-frame rolling window
                     plot = TRUE,
                     side = 'right')


5. Kinematic Analysis

Now we can compute kinematic variables like velocity, acceleration, and jerk.

Compute Velocity

Velocity is the rate of change of position. We compute it for all keypoints.

# Display a sample of the velocity data
df_velocity %>%
  filter(frame > 1 & frame < 6) %>%
  select(person, frame, region, v_1, v_4, v_7) %>% # Select Neck and Wrists
  nice_table()
Error in `select()`:
! Can't select columns that don't exist.
âś– Column `v_1` doesn't exist.
Run `]8;;x-r-run:rlang::last_trace()rlang::last_trace()]8;;` to see where the error occurred.

Compute Acceleration

Acceleration is the rate of change of velocity.

# We use the velocity dataframe as input
df_acceleration <- op_compute_acceleration(df_velocity, 
                                           merge_xy = TRUE,
                                           overwrite = FALSE,
                                           fps = 30)

# Display a sample of the velocity data
df_acceleration %>%
  filter(frame > 1 & frame < 6) %>%
  select(person, frame, region, a_1, a_4, a_7) %>% # Select Neck and Wrists
  nice_table()
Error in `select()`:
! Can't select columns that don't exist.
âś– Column `a_1` doesn't exist.
Run `]8;;x-r-run:rlang::last_trace()rlang::last_trace()]8;;` to see where the error occurred.

Compute Jerk

Jerk (or jerkiness) is the rate of change of acceleration, often used as a measure of movement smoothness.

base_filename

frame

region

person

v_Nose

v_Neck

v_RShoulder

v_RElbow

v_RWrist

v_LShoulder

v_LElbow

v_LWrist

v_MidHip

v_RHip

v_RKnee

v_RAnkle

v_LHip

v_LKnee

v_LAnkle

v_REye

v_LEye

v_REar

v_LEar

v_LBigToe

v_LSmallToe

v_LHeel

v_RBigToe

v_RSmallToe

v_RHeel

a_Nose

a_Neck

a_RShoulder

a_RElbow

a_RWrist

a_LShoulder

a_LElbow

a_LWrist

a_MidHip

a_RHip

a_RKnee

a_RAnkle

a_LHip

a_LKnee

a_LAnkle

a_REye

a_LEye

a_REar

a_LEar

a_LBigToe

a_LSmallToe

a_LHeel

a_RBigToe

a_RSmallToe

a_RHeel

j_Nose

j_Neck

j_RShoulder

j_RElbow

j_RWrist

j_LShoulder

j_LElbow

j_LWrist

j_MidHip

j_RHip

j_RKnee

j_RAnkle

j_LHip

j_LKnee

j_LAnkle

j_REye

j_LEye

j_REar

j_LEar

j_LBigToe

j_LSmallToe

j_LHeel

j_RBigToe

j_RSmallToe

j_RHeel

Dyad-10.0_B_IDs-B013-B016

2.00

body

left

1.21

0.72

0.60

0.49

87.18

1.01

82.62

85.78

1.41

2.34

87.25

0.00

1.78

121.06

0.00

0.91

82.30

0.45

1.40

0.00

0.00

0.00

0.00

0.00

0.00

Dyad-10.0_B_IDs-B013-B016

3.00

body

left

0.79

84.30

0.98

0.27

0.60

1.88

0.19

0.64

1.02

1.42

1.39

0.00

0.79

85.86

0.00

0.75

1.06

84.96

1.15

0.00

0.00

0.00

0.00

0.00

0.00

Dyad-10.0_B_IDs-B013-B016

4.00

body

left

82.15

0.45

1.14

0.13

124.38

0.47

0.95

125.36

82.86

82.89

0.36

0.00

83.94

121.16

0.00

84.42

84.37

0.79

82.02

0.00

0.00

0.00

0.00

0.00

0.00

2,449.03

2,533.71

17.86

12.01

3,743.10

43.99

27.42

3,779.84

2,469.84

2,467.75

37.43

0.00

2,503.91

5,775.91

0.00

2,511.93

2,512.83

2,529.91

2,426.57

0.00

0.00

0.00

0.00

0.00

0.00

Dyad-10.0_B_IDs-B013-B016

5.00

body

left

2.67

0.36

0.26

1.15

85.35

0.50

0.03

86.47

83.28

83.91

0.86

0.00

84.27

0.51

0.00

81.25

2.14

2.35

84.48

0.00

0.00

0.00

0.00

0.00

0.00

2,390.43

17.93

32.72

38.45

5,812.82

26.10

27.96

2,598.35

4,984.21

5,004.15

27.91

0.00

5,046.30

3,633.77

0.00

3,466.80

2,479.50

50.43

3,548.01

0.00

0.00

0.00

0.00

0.00

0.00

145,183.22

75,857.54

1,252.52

1,497.71

283,073.53

1,139.13

1,654.95

178,184.35

223,617.91

224,148.51

314.87

0.00

226,504.35

279,091.58

0.00

166,385.28

149,769.48

77,409.17

165,809.13

0.00

0.00

0.00

0.00

0.00

0.00

Dyad-10.0_B_IDs-B013-B016

2.00

body

right

0.97

82.02

85.22

83.70

0.03

83.13

85.21

122.38

85.77

83.79

123.04

0.00

0.49

0.60

0.00

0.30

86.39

1.35

83.92

0.00

0.00

0.00

0.00

0.00

0.00

894,714.73

916,397.08

924,879.22

838,819.33

769,788.12

927,496.05

858,490.16

912,430.98

787,137.68

788,366.07

745,521.55

0.00

797,344.54

672,503.66

0.00

916,333.25

920,394.59

958,649.81

1,350,168.67

0.00

0.00

0.00

0.00

0.00

0.00

53,680,632.98

54,977,173.53

55,332,761.46

50,176,766.05

46,187,137.70

55,648,031.36

51,430,963.95

54,755,948.40

47,225,834.22

47,291,535.14

44,820,155.94

0.00

47,839,361.08

40,344,729.86

0.00

54,894,928.58

55,150,722.93

57,517,757.59

94,151,052.14

0.00

0.00

0.00

0.00

0.00

0.00

Dyad-10.0_B_IDs-B013-B016

3.00

body

right

1.11

0.35

0.67

0.47

0.61

82.53

1.62

85.30

85.77

1.17

2.84

0.00

82.80

82.80

0.00

0.31

85.80

1.26

2.04

0.00

0.00

0.00

0.00

0.00

0.00

39.73

2,455.22

2,538.48

2,502.00

18.20

4,969.80

2,548.72

5,781.79

5,146.27

2,536.60

3,744.94

0.00

2,475.17

2,485.05

0.00

3.60

3,597.71

19.39

2,463.21

0.00

0.00

0.00

0.00

0.00

0.00

26,841,093.39

27,497,578.39

27,822,205.89

25,239,483.72

23,094,167.96

27,829,502.08

25,830,992.34

27,444,591.52

23,617,951.30

23,654,593.06

22,277,543.57

0.00

23,846,083.76

20,168,284.19

0.00

27,489,989.71

27,683,459.81

28,758,979.27

40,518,940.73

0.00

0.00

0.00

0.00

0.00

0.00

Dyad-10.0_B_IDs-B013-B016

4.00

body

right

1.38

81.81

1.50

0.48

0.30

0.99

0.42

0.64

85.77

0.89

2.66

0.00

83.10

0.18

0.00

82.52

2.71

2.55

0.31

0.00

0.00

0.00

0.00

0.00

0.00

60.08

2,459.77

59.57

26.76

27.13

2,446.20

59.49

2,565.57

5,146.23

50.98

164.79

0.00

4,977.09

2,478.62

0.00

2,484.61

2,538.48

114.35

60.35

0.00

0.00

0.00

0.00

0.00

0.00

2,992.13

301.87

75,330.08

74,795.31

1,359.69

222,480.00

76,003.87

244,953.91

308,774.95

77,540.04

115,558.60

0.00

223,566.89

148,908.92

0.00

74,540.66

170,850.20

3,542.66

72,184.91

0.00

0.00

0.00

0.00

0.00

0.00

Dyad-10.0_B_IDs-B013-B016

5.00

body

right

116.89

1.12

86.25

0.09

0.21

0.31

84.30

1.20

0.60

0.52

0.67

0.00

83.10

83.16

0.00

1.61

86.94

84.95

83.40

0.00

0.00

0.00

0.00

0.00

0.00

3,465.30

2,421.90

2,543.40

17.10

11.53

28.46

2,520.12

54.48

2,573.16

37.80

98.85

0.00

4,986.00

2,500.22

0.00

2,439.08

2,535.70

2,480.47

2,493.02

0.00

0.00

0.00

0.00

0.00

0.00

102,504.54

146,449.00

74,713.39

1,297.41

1,088.40

72,576.50

75,099.32

77,233.99

231,581.52

2,621.23

7,904.13

0.00

298,891.58

843.51

0.00

147,707.00

105,616.41

71,396.17

75,356.49

0.00

0.00

0.00

0.00

0.00

0.00

Average kinematics overtime for analysis


6. Motion Energy Analysis

Motion energy is a measure of the total amount of movement, calculated from the velocity of all keypoints.

# We use the 'df_jerk' dataframe as it contains all previously computed columns
# The function uses the velocity columns ('v_*') to compute motion energy
df_motion <- op_compute_motionenergy(df, 
                                     plot = TRUE)

# The resulting dataframe 'df_motion' now has a 'motion_energy' column
head(select(df_motion, frame, person, motion_energy))

7. Synchrony Analysis (Example for Dyadic Data)

Coherence analysis measures the synchrony between two time series (e.g., the motion energy of two people). It is therefore typically used for dyadic data. For this single-person tutorial, we will create a dummy second person’s data just to demonstrate the function’s syntax.

# Run coherance analysis for a single dyad
coherence_results <- op_compute_coherence(df_motion, plot = TRUE)
# View the coherence results summary
coherence_results

Session Information

This chunk documents the R version and loaded packages, which is crucial for reproducibility.

# Document your computational environment
sessionInfo()
LS0tCnRpdGxlOiAnRHVldCBQYWNrYWdlOiBTaW5nbGUgUGVyc29uIEFuYWx5c2lzIFdvcmtmbG93JwphdXRob3I6ICJZb3VyIE5hbWUiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICB0aGVtZTogZmxhdGx5Ci0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBlY2hvID0gVFJVRSwKICB3YXJuaW5nID0gRkFMU0UsCiAgbWVzc2FnZSA9IEZBTFNFLAogIGZpZy53aWR0aCA9IDEwLAogIGZpZy5oZWlnaHQgPSA2LAogIGRwaSA9IDMwMAopCmBgYAoKIyBEdWV0IFBhY2thZ2UgVHV0b3JpYWw6IFNpbmdsZSBQZXJzb24gQW5hbHlzaXMKClRoaXMgdHV0b3JpYWwgZGVtb25zdHJhdGVzIGEgY29tcGxldGUgd29ya2Zsb3cgZm9yIGFuYWx5emluZyBPcGVuUG9zZSBkYXRhIGZyb20gYSAqKnNpbmdsZSBpbmRpdmlkdWFsKiogdXNpbmcgdGhlIGBkdWV0YCBSIHBhY2thZ2UuCgotLS0KCiMjIDEuIFNldHVwIGFuZCBJbnN0YWxsYXRpb24KCiMjIyBJbnN0YWxsIFJlcXVpcmVkIFBhY2thZ2VzCgpUaGUgY2h1bmsgYmVsb3cgd2lsbCBpbnN0YWxsIGFueSBwYWNrYWdlcyB5b3UgYXJlIG1pc3NpbmcuIEl0IGlzIHNldCB0byBgZXZhbD1GQUxTRWAgc28gaXQgZG9lcyBub3QgcnVuIGF1dG9tYXRpY2FsbHkuIFJ1biBpdCBtYW51YWxseSBpbiB0aGUgUiBjb25zb2xlIGlmIHlvdSBuZWVkIHRvIGluc3RhbGwgcGFja2FnZXMgZm9yIHRoZSBmaXJzdCB0aW1lLgoKYGBge3IgaW5zdGFsbC1wYWNrYWdlc30KIyBJbnN0YWxsIGR1ZXQgZnJvbSBHaXRIdWIgaWYgeW91IGhhdmVuJ3QgYWxyZWFkeQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigidGhlbWlzZWZ0aC9kdWV0IikKCiMgQ29yZSBwYWNrYWdlcyBmb3IgdGhpcyB3b3JrZmxvdwpyZXF1aXJlZF9wYWNrYWdlcyA8LSBjKAogICJ0aWR5dmVyc2UiLCAiY293cGxvdCIsICJyZW1wc3ljIiwgIm1hZ2ljayIsICJmbGV4dGFibGUiLCAicmVtb3RlcyIpCgojIEluc3RhbGwgYW55IG1pc3NpbmcgcGFja2FnZXMgZnJvbSB0aGUgbGlzdApuZXdfcGFja2FnZXMgPC0gcmVxdWlyZWRfcGFja2FnZXNbIShyZXF1aXJlZF9wYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpWywiUGFja2FnZSJdKV0KaWYobGVuZ3RoKG5ld19wYWNrYWdlcykpIGluc3RhbGwucGFja2FnZXMobmV3X3BhY2thZ2VzKQpgYGAKCiMjIyBMb2FkIExpYnJhcmllcwoKYGBge3IgbG9hZC1saWJyYXJpZXN9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGR1ZXQpCmxpYnJhcnkobWFnaWNrKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeShyZW1wc3ljKQpsaWJyYXJ5KGZsZXh0YWJsZSkKCiMgU2V0IGEgY29uc2lzdGVudCB0aGVtZSBmb3IgZ2dwbG90MiBwbG90cwp0aGVtZV9zZXQodGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxMikpCmBgYAoKIyMjIENyZWF0ZSBEaXJlY3RvcnkgU3RydWN0dXJlCgpUaGlzIHdpbGwgY3JlYXRlIHRoZSBuZWNlc3NhcnkgZm9sZGVycyBpbiB5b3VyIHByb2plY3QgZGlyZWN0b3J5IHRvIHN0b3JlIHJhdyBkYXRhIGFuZCBvdXRwdXRzLgoKYGBge3IgY3JlYXRlLWRpcmVjdG9yaWVzfQpvdXRwdXRfZGlycyA8LSBjKCIuL29wZW5wb3NlX2pzb24iLCAiLi9vcGVucG9zZV9jc3YiLCAiLi9maWd1cmVzIikKZm9yKGRpcl9wYXRoIGluIG91dHB1dF9kaXJzKSB7CiAgaWYgKCFkaXIuZXhpc3RzKGRpcl9wYXRoKSkgZGlyLmNyZWF0ZShkaXJfcGF0aCwgcmVjdXJzaXZlID0gVFJVRSkKfQpgYGAKCi0tLQoKIyMgMi4gRGF0YSBDb252ZXJzaW9uOiBKU09OIHRvIENTVgoKVGhlIGZpcnN0IHN0ZXAgaXMgdG8gY29udmVydCB0aGUgcmF3IEpTT04gZmlsZXMgZnJvbSBPcGVuUG9zZSBpbnRvIGEgc2luZ2xlLCB0aWR5IENTViBmaWxlLgoKIyMjIFVuZGVyc3RhbmRpbmcgWW91ciBEYXRhIFN0cnVjdHVyZQoKUGxhY2UgeW91ciByYXcgT3BlblBvc2UgSlNPTiBmaWxlcyBpbiBhIGRlZGljYXRlZCBmb2xkZXIuIEZvciB0aGlzIGV4YW1wbGUsIHdlIGFzc3VtZSB0aGV5IGFyZSBpbiBgLi9vcGVucG9zZV9qc29uL0luZGl2aWR1YWxfMDEvYC4KCmBgYHRleHQKLi9vcGVucG9zZV9qc29uLwrilJzilIDilIAgSW5kaXZpZHVhbF8wMS8K4pSCICAg4pSc4pSA4pSAIGZyYW1lXzAwMDAwMV9rZXlwb2ludHMuanNvbgrilIIgICDilJzilIDilIAgZnJhbWVfMDAwMDAyX2tleXBvaW50cy5qc29uCuKUgiAgIOKUlOKUgOKUgCAuLi4K4pSU4pSA4pSAIC4uLgpgYGAKCiMjIyBDb252ZXJ0IEpTT04gdG8gYSBTaW5nbGUgUGVyc29uIENTVgoKVGhpcyBmdW5jdGlvbiByZWFkcyBhbGwgSlNPTiBmaWxlcyBpbiB0aGUgc3BlY2lmaWVkIGRpcmVjdG9yeSBhbmQgY29udmVydHMgdGhlbSBpbnRvIG9uZSBDU1YgZmlsZS4gV2Ugc2V0IGBleHBvcnRfdHlwZSA9ICdwZXJzb24nYCBhbmQgc3BlY2lmeSBgcGVyc29uID0gMWAgdG8gZW5zdXJlIHdlIG9ubHkgcHJvY2VzcyBkYXRhIGZvciB0aGUgZmlyc3QgcGVyc29uIGRldGVjdGVkIGJ5IE9wZW5Qb3NlLgoKYGBge3IgY29udmVydC1zaW5nbGUtZHlhZH0Kb3BfY3JlYXRlX2NzdigKICBpbnB1dF9wYXRoID0gJy4vb3BlbnBvc2VfanNvbi9EeWFkXzEwJywKICBvdXRwdXRfcGF0aCA9ICcuL29wZW5wb3NlX2Nzdi9EeWFkXzEwJywKICBtb2RlbCA9ICdib2R5JywKICBpbmNsdWRlX2ZpbGVuYW1lID0gVFJVRSwKICBleHBvcnRfdHlwZSA9ICdkeWFkJykKYGBgCgotLS0KCiMjIDMuIERhdGEgSW1wb3J0IGFuZCBJbml0aWFsIENoZWNrcwoKIyMjIEltcG9ydCBEYXRhCgpMb2FkIHRoZSBDU1YgZmlsZSB5b3UganVzdCBjcmVhdGVkLgoKYGBge3IgaW1wb3J0LWRhdGF9CiMgUmVhZCB0aGUgQ1NWIGZpbGUgaW50byBhIGRhdGFmcmFtZQpkZiA8LSByZWFkX2NzdignLi9vcGVucG9zZV9jc3YvRHlhZF8xMC9EeWFkLTEwLjBfQl9JRHMtQjAxMy1CMDE2X2JvZHlfZHlhZC5jc3YnKQpgYGAKCiMjIyBDaGVjayBEYXRhCgpJdCdzIGdvb2QgcHJhY3RpY2UgdG8gdmlzdWFsbHkgaW5zcGVjdCBhIGZyYW1lIHRvIGVuc3VyZSB0aGUgZGF0YSBoYXMgbG9hZGVkIGNvcnJlY3RseS4KCmBgYHtyIGNoZWNrLWRhdGF9CiMgUGxvdCBhIHNpbmdsZSBmcmFtZSB0byBjaGVjayB0aGUgcG9zZQpvcF9wbG90X29wZW5wb3NlKGRmLCAKICAgICAgICAgICAgICAgICBwZXJzb24gPSAiYm90aCIsCiAgICAgICAgICAgICAgICAgZnJhbWVfbnVtID0gMTAsIAogICAgICAgICAgICAgICAgIGxpbmVzID0gVFJVRSkKYGBgCgotLS0KCiMjIDQuIERhdGEgUXVhbGl0eSBhbmQgUHJlcHJvY2Vzc2luZwoKIyMjIEFzc2VzcyBEYXRhIFF1YWxpdHkKClRoZSBgb3BfcGxvdF9xdWFsaXR5KClgIGZ1bmN0aW9uIGhlbHBzIHlvdSB1bmRlcnN0YW5kIHRoZSBjb21wbGV0ZW5lc3MgKGhvdyBtYW55IGtleXBvaW50cyB3ZXJlIGRldGVjdGVkKSBhbmQgY29uZmlkZW5jZSAoT3BlblBvc2UncyBjZXJ0YWludHkpIG9mIHlvdXIgZGF0YSBvdmVyIHRpbWUuCgpgYGB7ciBwbG90LXF1YWxpdHl9CiMgRmlyc3QsIGFwcGx5IGh1bWFuLXJlYWRhYmxlIGxhYmVscyB0byB0aGUga2V5cG9pbnRzCmRmX2xhYmVsbGVkIDwtIG9wX2FwcGx5X2tleXBvaW50X2xhYmVscyhkZikKCiMgUGxvdCBkYXRhIGNvbXBsZXRlbmVzcwpwX2NvbXBsZXRlbmVzcyA8LSBvcF9wbG90X3F1YWxpdHkoZGZfbGFiZWxsZWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF90eXBlID0gJ2NvbXBsZXRlbmVzcycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJlc2hvbGRfbGluZSA9IDgwKSAjIDgwJSB0aHJlc2hvbGQgbGluZQoKIyBQbG90IHRyYWNraW5nIGNvbmZpZGVuY2UKcF9jb25maWRlbmNlIDwtIG9wX3Bsb3RfcXVhbGl0eShkZl9sYWJlbGxlZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF90eXBlID0gJ2NvbmZpZGVuY2UnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZF9saW5lID0gODApICMgQ29uZmlkZW5jZSB0aHJlc2hvbGQgb2YgODAlCgojIENvbWJpbmUgcGxvdHMgZm9yIGEgY29tcHJlaGVuc2l2ZSB2aWV3CnBfcXVhbGl0eV9jb21iaW5lZCA8LSBwbG90X2dyaWQocF9jb21wbGV0ZW5lc3MsIHBfY29uZmlkZW5jZSwgbmNvbCA9IDEsIGxhYmVscyA9ICdhdXRvJykKCnBfcXVhbGl0eV9jb21iaW5lZApgYGAKCiMjIyBTdW1tYXJpc2UgeW91ciBkYXRhCgpBdCBhbnkgc3RhZ2UgeW91IGNhbiBwcm9kdWNlIHN1bW1hcnkgc3RhdGlzdGljcyAobWVhbiwgbWVkaWFuLCBza2V3IGV0YykgZm9yIHlvdXIgZGYKCmBgYHtyfQpvcF9zdW1tYXJpc2UoZGYpCmBgYAoKIyMjIFZpc3VhbGlzZSBUaW1lIFNlcmllcwoKUGxvdCB0aGUgbW92ZW1lbnQgb2Ygc3BlY2lmaWMga2V5cG9pbnRzIG92ZXIgdGltZSB0byBpZGVudGlmeSBub2lzZSBvciBnYXBzLgoKYGBge3IgcGxvdC10aW1lc2VyaWVzfQojIFBsb3QgdGhlIHggYW5kIHkgY29vcmRpbmF0ZXMgZm9yIE5vc2UgKDEpIGFuZCBXcmlzdHMgKDQsIDcpCnBfdGltZXNlcmllcyA8LSBvcF9wbG90X3RpbWVzZXJpZXMoZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyZWVfeSA9IFRSVUUsICMgQWxsb3cgeS1heGVzIHRvIGhhdmUgZGlmZmVyZW50IHNjYWxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG92ZXJsYXkgPSBUUlVFLCAjIE92ZXJsYXkgeCBhbmQgeSBvbiB0aGUgc2FtZSBwbG90CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2ZhY2V0cyA9IDIwKSAKCnBfdGltZXNlcmllcwpgYGAKCiMjIyBJbnRlcnBvbGF0ZSBtaXNzaW5nIGRhdGEKCmBgYHtyfQpkZl9pbnRlcnAgPC0gb3BfaW50ZXJwb2xhdGUoZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uZmlkZW5jZV90aHJlc2hvbGQgPSAuMywgIyBTZXQgYSBjb25maWRlbmNlIHRocmVzaG9sZCBvZiA4MCUKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyZWF0X25hX2NvbmZfYXNfbG93ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhbmRsZV96ZXJvcyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBoYW5kbGVfbWlzc2luZyA9IFRSVUUpCgpwX3RpbWVzZXJpZXNfaW50ZXJwIDwtIG9wX3Bsb3RfdGltZXNlcmllcyhkZl9pbnRlcnAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyZWVfeSA9IFRSVUUsICMgQWxsb3cgeS1heGVzIHRvIGhhdmUgZGlmZmVyZW50IHNjYWxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG92ZXJsYXkgPSBUUlVFLCAjIE92ZXJsYXkgeCBhbmQgeSBvbiB0aGUgc2FtZSBwbG90CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2ZhY2V0cyA9IDIwKSAKCnBfdGltZXNlcmllc19pbnRlcnAKYGBgCgoKIyMjIFNtb290aCBUaW1lIFNlcmllcyBEYXRhCgpVc2UgYSBtb3ZpbmcgYXZlcmFnZSB0byBzbW9vdGggdGhlIGRhdGEsIHdoaWNoIGlzIG9mdGVuIG5lY2Vzc2FyeSBiZWZvcmUgY29tcHV0aW5nIHZlbG9jaXR5IG9yIGFjY2VsZXJhdGlvbi4gSGVyZSwgd2Ugc21vb3RoIHRoZSB4LWNvb3JkaW5hdGUgb2YgdGhlIGBOZWNrYCBrZXlwb2ludCBhcyBhbiBleGFtcGxlLgoKYGBge3Igc21vb3RoLWRhdGF9CiMgQXBwbHkgbW92aW5nLWF2ZXJhZ2Ugc21vb3RoaW5nIGFuZCBwbG90IHRoZSByZXN1bHQKb3Bfc21vb3RoX3RpbWVzZXJpZXMoZGYsIAogICAgICAgICAgICAgICAgICAgICBrZXlwb2ludHMgPSBjKCd4MScpLCAjIEtleXBvaW50ICcxJyBpcyB0aGUgTmVjawogICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAncm9sbG1lYW4nLAogICAgICAgICAgICAgICAgICAgICByb2xsbWVhbl93aWR0aCA9IDUsICMgNS1mcmFtZSByb2xsaW5nIHdpbmRvdwogICAgICAgICAgICAgICAgICAgICBwbG90ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgc2lkZSA9ICdyaWdodCcpCmBgYAoKLS0tCgojIyA1LiBLaW5lbWF0aWMgQW5hbHlzaXMKCk5vdyB3ZSBjYW4gY29tcHV0ZSBraW5lbWF0aWMgdmFyaWFibGVzIGxpa2UgdmVsb2NpdHksIGFjY2VsZXJhdGlvbiwgYW5kIGplcmsuCgojIyMgQ29tcHV0ZSBWZWxvY2l0eQoKVmVsb2NpdHkgaXMgdGhlIHJhdGUgb2YgY2hhbmdlIG9mIHBvc2l0aW9uLiBXZSBjb21wdXRlIGl0IGZvciBhbGwga2V5cG9pbnRzLgoKYGBge3IgY29tcHV0ZS12ZWxvY2l0eX0KZGYgPC0gb3BfYXBwbHlfa2V5cG9pbnRfbGFiZWxzKGRmKQoKIyBDb21wdXRlIHZlbG9jaXR5IGFuZCBhZGQgaXQgdG8gdGhlIGRhdGFmcmFtZQpkZl92ZWxvY2l0eSA8LSBvcF9jb21wdXRlX3ZlbG9jaXR5KGRmLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXJnZV94eSA9IFRSVUUsICMgQ29tYmluZSB4IGFuZCB5IHZlbG9jaXR5CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3ZlcndyaXRlID0gRkFMU0UsICMgVGhpcyBkZWxldGVzIHlvdXIgb3JpZ2luYWwgY29sdW1ucyBmb3IgYSB0aWRpZXIgZGF0YWZyYW1lLCByZXRhaW4gaWYgeW91IHBsYW4gdG8gY29tcHV0ZSBhY2NlbGVyYXRpb24gYW5kIGplcmsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcHMgPSAzMCkgIyBZb3VyIHZpZGVvJ3MgZnJhbWUgcmF0ZQoKIyBEaXNwbGF5IGEgc2FtcGxlIG9mIHRoZSB2ZWxvY2l0eSBkYXRhCmRmX3ZlbG9jaXR5ICU+JQogIGZpbHRlcihmcmFtZSA+IDEgJiBmcmFtZSA8IDYpICU+JQogIG5pY2VfdGFibGUoKQpgYGAKCiMjIyBDb21wdXRlIEFjY2VsZXJhdGlvbgoKQWNjZWxlcmF0aW9uIGlzIHRoZSByYXRlIG9mIGNoYW5nZSBvZiB2ZWxvY2l0eS4KCmBgYHtyIGNvbXB1dGUtYWNjZWxlcmF0aW9ufQojIFdlIHVzZSB0aGUgdmVsb2NpdHkgZGF0YWZyYW1lIGFzIGlucHV0CmRmX2FjY2VsZXJhdGlvbiA8LSBvcF9jb21wdXRlX2FjY2VsZXJhdGlvbihkZl92ZWxvY2l0eSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXJnZV94eSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdmVyd3JpdGUgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZwcyA9IDMwKQoKIyBEaXNwbGF5IGEgc2FtcGxlIG9mIHRoZSB2ZWxvY2l0eSBkYXRhCmRmX2FjY2VsZXJhdGlvbiAlPiUKICBmaWx0ZXIoZnJhbWUgPiAxICYgZnJhbWUgPCA2KSAlPiUKICBuaWNlX3RhYmxlKCkKYGBgCgojIyMgQ29tcHV0ZSBKZXJrCgpKZXJrIChvciBqZXJraW5lc3MpIGlzIHRoZSByYXRlIG9mIGNoYW5nZSBvZiBhY2NlbGVyYXRpb24sIG9mdGVuIHVzZWQgYXMgYSBtZWFzdXJlIG9mIG1vdmVtZW50IHNtb290aG5lc3MuCgpgYGB7ciBjb21wdXRlLWplcmt9CiMgV2UgdXNlIHRoZSBhY2NlbGVyYXRpb24gZGF0YWZyYW1lIGFzIGlucHV0CmRmX2plcmsgPC0gb3BfY29tcHV0ZV9qZXJrKGRmX2FjY2VsZXJhdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVyZ2VfeHkgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICBvdmVyd3JpdGUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICBmcHMgPSAzMCkKCiMgRGlzcGxheSBhIHNhbXBsZSBvZiB0aGUgdmVsb2NpdHkgZGF0YQpkZl9qZXJrICU+JQogIGZpbHRlcihmcmFtZSA+IDEgJiBmcmFtZSA8IDYpICU+JQogIG5pY2VfdGFibGUoKQpgYGAKCiMjIyBBdmVyYWdlIGtpbmVtYXRpY3Mgb3ZlcnRpbWUgZm9yIGFuYWx5c2lzCgpgYGB7ciBhdmVyYWdlLWtpbmVtYXRpY3N9CiMgQWdncmVnYXRlIHJlc3VsdHMgdG8gYm9keSBwYXJ0cyBhbmQgdGhlbiB0byBvdmVyYWxsIG1lYXN1cmVzCmtpbmVtYXRpY3Nfc3VtbWFyeV9iYXRjaCA8LSBkZl9qZXJrICU+JQogIHNlbGVjdChiYXNlX2ZpbGVuYW1lLCBwZXJzb24sIHN0YXJ0c193aXRoKCJ2XyIpLCBzdGFydHNfd2l0aCgiYV8iKSwgc3RhcnRzX3dpdGgoImpfIikpICU+JQogIGdyb3VwX2J5KGJhc2VfZmlsZW5hbWUsIHBlcnNvbikgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhldmVyeXRoaW5nKCksIH5tZWFuKC54LCBuYS5ybSA9IFRSVUUpKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgcm93d2lzZSgpICU+JQogIHRyYW5zbXV0ZSgKICAgIGJhc2VfZmlsZW5hbWUsIHBlcnNvbiwgCiAgICB2X2hlYWQgPSBtZWFuKGModl9Ob3NlLCB2X1JFeWUsIHZfTEV5ZSksIG5hLnJtID0gVFJVRSksCiAgICBhX2hlYWQgPSBtZWFuKGMoYV9Ob3NlLCBhX1JFeWUsIGFfTEV5ZSksIG5hLnJtID0gVFJVRSksCiAgICBqX2hlYWQgPSBtZWFuKGMoal9Ob3NlLCBqX1JFeWUsIGpfTEV5ZSksIG5hLnJtID0gVFJVRSksCiAgICB2X2FybXMgPSBtZWFuKGModl9SV3Jpc3QsIHZfUkVsYm93LCB2X0xXcmlzdCwgdl9MRWxib3cpLCBuYS5ybSA9IFRSVUUpLAogICAgYV9hcm1zID0gbWVhbihjKGFfUldyaXN0LCBhX1JFbGJvdywgYV9MV3Jpc3QsIGFfTEVsYm93KSwgbmEucm0gPSBUUlVFKSwKICAgIGpfYXJtcyA9IG1lYW4oYyhqX1JXcmlzdCwgal9SRWxib3csIGpfTFdyaXN0LCBqX0xFbGJvdyksIG5hLnJtID0gVFJVRSksCiAgICB2ZWxvY2l0eV90b3RhbCA9IG1lYW4oYyh2X2hlYWQsIHZfYXJtcyksIG5hLnJtID0gVFJVRSksCiAgICBhY2NlbGVyYXRpb25fdG90YWwgPSBtZWFuKGMoYV9oZWFkLCBhX2FybXMpLCBuYS5ybSA9IFRSVUUpLAogICAgamVya190b3RhbCA9IG1lYW4oYyhqX2hlYWQsIGpfYXJtcyksIG5hLnJtID0gVFJVRSkKICApICU+JQogIHVuZ3JvdXAoKQoKa2luZW1hdGljc19zdW1tYXJ5X2JhdGNoCgojIFlvdSBjYW4gbm93IHNhdmUgdGhpcyBmaWxlIGZvciBmdXJ0aGVyIGFuYWx5c2lzCmBgYAoKLS0tCgojIyA2LiBNb3Rpb24gRW5lcmd5IEFuYWx5c2lzCgpNb3Rpb24gZW5lcmd5IGlzIGEgbWVhc3VyZSBvZiB0aGUgdG90YWwgYW1vdW50IG9mIG1vdmVtZW50LCBjYWxjdWxhdGVkIGZyb20gdGhlIHZlbG9jaXR5IG9mIGFsbCBrZXlwb2ludHMuCgpgYGB7ciBjb21wdXRlLW1vdGlvbi1lbmVyZ3l9CiMgV2UgdXNlIHRoZSAnZGZfamVyaycgZGF0YWZyYW1lIGFzIGl0IGNvbnRhaW5zIGFsbCBwcmV2aW91c2x5IGNvbXB1dGVkIGNvbHVtbnMKIyBUaGUgZnVuY3Rpb24gdXNlcyB0aGUgdmVsb2NpdHkgY29sdW1ucyAoJ3ZfKicpIHRvIGNvbXB1dGUgbW90aW9uIGVuZXJneQpkZl9tb3Rpb24gPC0gb3BfY29tcHV0ZV9tb3Rpb25lbmVyZ3koZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdCA9IFRSVUUpCgojIFRoZSByZXN1bHRpbmcgZGF0YWZyYW1lICdkZl9tb3Rpb24nIG5vdyBoYXMgYSAnbW90aW9uX2VuZXJneScgY29sdW1uCmhlYWQoc2VsZWN0KGRmX21vdGlvbiwgZnJhbWUsIHBlcnNvbiwgbW90aW9uX2VuZXJneSkpCmBgYAoKLS0tCgojIyA3LiBTeW5jaHJvbnkgQW5hbHlzaXMgKEV4YW1wbGUgZm9yIER5YWRpYyBEYXRhKQoKQ29oZXJlbmNlIGFuYWx5c2lzIG1lYXN1cmVzIHRoZSBzeW5jaHJvbnkgYmV0d2VlbiB0d28gdGltZSBzZXJpZXMgKGUuZy4sIHRoZSBtb3Rpb24gZW5lcmd5IG9mIHR3byBwZW9wbGUpLiBJdCBpcyB0aGVyZWZvcmUgdHlwaWNhbGx5IHVzZWQgZm9yIGR5YWRpYyBkYXRhLiBGb3IgdGhpcyBzaW5nbGUtcGVyc29uIHR1dG9yaWFsLCB3ZSB3aWxsIGNyZWF0ZSBhICoqZHVtbXkgc2Vjb25kIHBlcnNvbidzIGRhdGEqKiBqdXN0IHRvIGRlbW9uc3RyYXRlIHRoZSBmdW5jdGlvbidzIHN5bnRheC4KCmBgYHtyIGNvbXB1dGUtY29oZXJlbmNlfQojIFJ1biBjb2hlcmFuY2UgYW5hbHlzaXMgZm9yIGEgc2luZ2xlIGR5YWQKY29oZXJlbmNlX3Jlc3VsdHMgPC0gb3BfY29tcHV0ZV9jb2hlcmVuY2UoZGZfbW90aW9uLCBwbG90ID0gVFJVRSkKYGBgCgpgYGB7ciBvdXRwdXQtY29oZXJlbmNlfQojIFZpZXcgdGhlIGNvaGVyZW5jZSByZXN1bHRzIHN1bW1hcnkKY29oZXJlbmNlX3Jlc3VsdHMKYGBgCgotLS0KCiMjIFNlc3Npb24gSW5mb3JtYXRpb24KClRoaXMgY2h1bmsgZG9jdW1lbnRzIHRoZSBSIHZlcnNpb24gYW5kIGxvYWRlZCBwYWNrYWdlcywgd2hpY2ggaXMgY3J1Y2lhbCBmb3IgcmVwcm9kdWNpYmlsaXR5LgoKYGBge3Igc2Vzc2lvbi1pbmZvfQojIERvY3VtZW50IHlvdXIgY29tcHV0YXRpb25hbCBlbnZpcm9ubWVudApzZXNzaW9uSW5mbygpCg==